其他
RecyclerView 的缓存复用机制
https://github.com/samwangds/AndroidInterview
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<>();
RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
}
一级缓存:mAttachedScrap 和 mChangedScrap ,用来缓存还在屏幕内的 ViewHolder
mAttachedScrap 存储的是当前还在屏幕中的 ViewHolder;按照 id 和 position 来查找 ViewHolder mChangedScrap 表示数据已经改变的 ViewHolder 列表, 存储 notifyXXX 方法时需要改变的 ViewHolder
二级缓存:mCachedViews ,用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存容量是 2,可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 FIFO 的规则移除旧 ViewHolder
三级缓存:ViewCacheExtension ,开发给用户的自定义扩展缓存,需要用户自己管理 View 的创建和缓存。个人感觉这个拓展脱离了 Adapter.createViewHolder 使用的话会造成 View 创建 与 数据绑定及其它代码太分散,不利于维护,使用场景很少仅做了解
* Note that, Recycler never sends Views to this method to be cached. It is developers
* responsibility to decide whether they want to keep their Views in this custom cache or let
* the default recycling policy handle it.
*/
public abstract static class ViewCacheExtension {
public abstract View getViewForPositionAndType(...);
}
四级缓存:RecycledViewPool ,ViewHolder 缓存池,在有限的 mCachedViews 中如果存不下新的 ViewHolder 时,就会把 ViewHolder 存入RecyclerViewPool 中。 按照 Type 来查找 ViewHolder 每个 Type 默认最多缓存 5 个 可以多个 RecyclerView 共享 RecycledViewPool
-> RecyclerView.dispatchLayout()
-> RecyclerView.dispatchLayoutStep2() // do the actual layout of the views for the final state.
-> mLayout.onLayoutChildren(mRecycler, mState) // mLayout 类型为 LayoutManager
-> LinearLayoutManager.onLayoutChildren(...) // 以 LinearLayoutManager 为例
-> LinearLayoutManager.fill(...) // The magic functions :) 填充给定的布局,注释很自信的说这个方法很独立,稍微改动就能作为帮助类的一个公开方法,程序员的快乐就是这么朴实无华。
-> LinearLayoutManager.layoutChunk(recycler, layoutState) // 循环调用,每次调用填充一个 ItemView 到 RV
-> LinearLayoutManager.LayoutState.next(recycler)
-> RecyclerView.Recycler.getViewForPosition(int) // 回到主角了,通过 Recycler 获取指定位置的 ItemView
-> Recycler.getViewForPosition(int, boolean) // 调用下面方法获取 ViewHolder,并返回上面需要的 viewHolder.itemView
-> Recycler.tryGetViewHolderForPositionByDeadline(...) // 终于找到你,还好没放弃~
if (mState.isPreLayout()) {
// 0) 预布局从 mChangedScrap 里面去获取 ViewHolder,动画相关
holder = getChangedScrapViewForPosition(position);
}
if (holder == null) {
// 1) 分别从 mAttachedScrap、 mHiddenViews、mCachedViews 获取 ViewHolder
// 这个 mHiddenViews 是用来做动画期间的复用
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
if (holder == null) {
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) 如果 Adapter 的 hasStableIds 方法返回为 true
// 优先通过 ViewType 和 ItemId 两个条件从 mAttachedScrap 和 mCachedViews 寻找
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
}
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not know it.
// 3) 从自定义缓存获取,别问,问就是别用
View view = mViewCacheExtension
getViewForPositionAndType(this, position, type);
holder = getChildViewHolder(view);
}
}
if (holder == null) {
// 4) 从 RecycledViewPool 获取 ViewHolder
holder = getRecycledViewPool().getRecycledView(type);
}
if (holder == null) {
// 缓存全取过了,没有,那只好 create 一个咯
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
// This is very ugly but the only place we can grab this information
// 大半夜看到这句注释的时候笑出声!像极了我写出丑代码时的无奈。
// 在这后面有一些刷新 ViewHolder 信息的代码,放这很丑,但又只能放这; 为了能走到这,前面有多少 if (holder == null)
}
mAttachedScrap 和 mChangedScrap mCachedViews ViewCacheExtension 前面说了,这个的创建和缓存完全由开发者自己控制,系统未往这里添加数据 RecycledViewPool
mAttachedScrap 和 mChangedScrap
LinearLayoutManager.onLayoutChildren(...)
-> LayoutManager.detachAndScrapAttachedViews(recycler)
-> LayoutManager.scrapOrRecycleView(..., view)
-> Recycler.scrapView(view);
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
// 后面会讲,缓存到 mCacheViews 和 RecyclerViewPool
recycler.recycleViewHolderInternal(viewHolder);
} else {
// 缓存到 scrap
recycler.scrapView(view);
}
}
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
// 标记为移除或失效的 || 完全没有改变 || item 无动画或动画不复用
mAttachedScrap.add(holder);
} else {
// 相反就得出放入 mChangedScrap 的条件啦
mChangedScrap.add(holder);
}
}
-> Recycler.getScrapOrHiddenOrCachedHolderForPosition(...)
-> Recycler.scrapView(view)
mCacheViews 和 RecyclerViewPool
if (forceRecycle || holder.isRecyclable()) {
if(mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
// 1. mCacheViews 满了,最早加入的不要了放 RecyclerViewPool
recycleCachedViewAt(0);
}
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
// 2. 不能放进 mCacheViews 的放 RecyclerViewPool
addViewHolderToRecycledViewPool(holder, true);
}
}
}
// Recycles a cached view and removes the view from the list
void recycleCachedViewAt(int cachedViewIndex) {
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
addViewHolderToRecycledViewPool(viewHolder, true);
mCachedViews.remove(cachedViewIndex);
}
重新布局,主要是调用 Adapter.notifyDataSetChange 且 Adapter 的 hasStableIds 方法返回为 false 时调用。从这边也可以看出为什么一般情况下 notifyDataSetChange 效率比其它 notifyXXX 方法低(使用二级缓存及优先级更低的缓存 ),同时也知道了,如果我们设置 Adapter.setHasStableIds(ture) 以及其它相关需要的实现,则可以提高效率(使用一级缓存) 在复用时,从一级缓存里面获取到 ViewHolder,但是此时这个 ViewHolder 已经不符合一级缓存的特点了(比如 Position 失效了,跟 ViewType 对不齐),就会从一级缓存里面移除这个 ViewHolder,添加到这两级缓存里面 当调用 removeAnimatingView 方法时,如果当前 ViewHolder 被标记为 remove ,会调用 recycleViewHolderInternal 方法来回收对应的 ViewHolder。调用 removeAnimatingView 方法的时机表示当前的 ItemAnimator 已经做完了
RecyclerView 的缓存复用机制,主要是通过内部类 Recycler 来实现 Recycler 有 4 级缓存,每一级的缓存都有各自的作用,会按优先级使用。 ViewHolder 会从某一级缓存移至其它级别的缓存 mHideenViews 的存在是为了解决在动画期间进行复用的问题。 缓存复用 ViewHolder 时会针对内部不同的状态 (mFlags) 进行相应的处理。